/* chat parrot v0.4 - MrJonze Independent 2017-05
 * no filtering - posts everything said on PUBLIC_CHANNEL
 * polls slowly if no responses - stops polling when no chat
 * rewrite for rails server
 * throttling still untested
 * TODO: get list of active clients and # chatters (how often?)
 */

integer DEBUG = FALSE ;
integer DEBUGSND = FALSE; integer nPolls = 0;


string  SERVER_URL        = "https://82f640d1.ngrok.io" ;
integer SHOULD_VERIFY_SSL = TRUE ;

string GRID_NAME     = "SecondLife" ;
string CLIENTS_PATH  = "/test_clients.json" ;
string MESSAGE_PATH  = "/clients/{{ROOM_NAME}}/message.json" ;
string MESSAGES_PATH = "/clients/{{ROOM_NAME}}/messages.json" ;
string OWNER_NAME ;
string REGION_NAME ;
string PARCEL_NAME ;
string ROOM_NAME ;
string CLIENTS_URL ;
string MESSAGE_URL ;
string MESSAGES_URL ;
string CLIENT_SECRET ;
string CLIENTS_JSON ;

// dialog prompts
integer DLG_CH      = -99 ;
string  DLG_PROMPT  = "Polly wanna shut up?" ;
string  OFF_DLG_BTN = "Off" ;
string  ON_DLG_BTN  = "On" ;

// JSON string parsing
string ROOM_NAME_SENTINAL = "{{ROOM_NAME}}" ;
string MSGS_JSON_HEAD     = "{\"secret\":\"" ;
string MSGS_JSON_MID      = "\",\"ts\":\"" ;
string MSGS_JSON_TAIL     = "\"}" ;
string ID_TOKEN           = "\"id\":\"" ;
string NICK_TOKEN         = "\"nick\":\"" ;
string TEXT_TOKEN         = "\"text\":\"" ;
string TS_TOKEN           = "\"created_at\":\"" ;
string END_TOKEN          = "\",\"" ;

// http params
list JSON_POST_PARAMS = [ HTTP_METHOD , "POST" , HTTP_MIMETYPE , "application/json" ,
                          HTTP_VERIFY_CERT , FALSE                                  ] ;

// poll frequencies
float TIMEOUT  = 1500.0 ;
float SLOWPOLL = 300.0 ;
float MEDPOLL  = 60.0 ;
float FASTPOLL = 2.0 ;

// misc
integer N_MESSAGE_PARAMS = 3 ;

// runtime vars
list    Messages       = [] ;
float   PollRate       = 0.0 ;
integer LastDlg        = 0 ;
integer LastLocalChat  = 0 ;
integer LastRemoteChat = 0 ;
string  LastMsgTs      = "1970-01-01T00:00:00.000Z" ;
key     MessagesReqId ;
integer ChatListener ;
integer CtrlListener ;


listenDbg(string msg)
{
//  llOwnerSay("i heard= \"" + msg + "\"");
if (DEBUGSND) llPlaySound("Clear throat Male", 1.0);
}

timerDbg()
{
        integer now = llGetUnixTime();// - 60;
        if ((now - LastDlg) > MEDPOLL)
            llListenRemove(CtrlListener);

string speed;
        if ((now - LastLocalChat) > TIMEOUT)
            { setPollRate(0.0); speed = "TIMEOUT"; }
        else if ((now - LastRemoteChat) > SLOWPOLL)
            { setPollRate(SLOWPOLL); speed = "SLOWPOLL"; }
        else if ((now - LastRemoteChat) > MEDPOLL)
            { setPollRate(MEDPOLL); speed = "MEDPOLL"; }
            
llOwnerSay(speed + " - " + (string)llGetListLength(Messages) + " lines pending");
llOwnerSay("lastloc= " + (string)((integer)(now - LastLocalChat)) + "s" +
        " - lastrem= " + (string)((integer)(now - LastRemoteChat)) + "s");
if (DEBUGSND) llPlaySound("Whistle call", 1.0);
}

httpDbgIn(string data)
{ /*llOwnerSay("resp=" + data + "\n"); if (DEBUGSND && (integer)data != 200) llPlaySound("Cry Female", 1.0);*/ }

httpDbgOk(string data)
{ llOwnerSay("FASTPOLL"); if (DEBUGSND) llPlaySound("Looking good Female", 1.0); }

httpDbgOut(string data)
{
        list Messages = llParseString2List(data, ["\n"], []);
        integer i; integer len = llGetListLength(Messages);
//if (len) llOwnerSay("nChatLines= " + (string)len);
        for (i = 0; i < len; i++)
llOwnerSay(llUnescapeURL(llList2String(Messages, i)));
//if (llGetSubString(llStringTrim(llList2String(Messages, i), STRING_TRIM_HEAD), 0, 6) ==   "<title>") llOwnerSay(llList2String(Messages, i));
}


/* state default functions */

init()
{
  list parcel_details = llGetParcelDetails(llGetPos() , [PARCEL_DETAILS_NAME]) ;
  OWNER_NAME          = llKey2Name(llGetOwner()) ;
  REGION_NAME         = strReplace(llGetRegionName()                 , " " , "_") ;
  PARCEL_NAME         = strReplace(llList2String(parcel_details , 0) , " " , "_") ;
  ROOM_NAME           = llDumpList2String([ GRID_NAME , REGION_NAME , PARCEL_NAME ] , " ") ;

  MESSAGE_PATH     = strReplace(MESSAGE_PATH  , ROOM_NAME_SENTINAL , ROOM_NAME) ;
  MESSAGES_PATH    = strReplace(MESSAGES_PATH , ROOM_NAME_SENTINAL , ROOM_NAME) ;
  CLIENTS_URL      = llToLower(SERVER_URL) + CLIENTS_PATH ;
  MESSAGE_URL      = llToLower(SERVER_URL) + MESSAGE_PATH ;
  MESSAGES_URL     = llToLower(SERVER_URL) + MESSAGES_PATH ;
  JSON_POST_PARAMS = llListReplaceList(JSON_POST_PARAMS , SHOULD_VERIFY_SSL , -1 , -1) ;
  CLIENTS_JSON     = "{ \"client\": { \"room\":  \"" + ROOM_NAME  + "\" , " +
                                     "\"nicks\": \"" + OWNER_NAME + "\" , " +
                                     "\"type\":  \"SimClient\"         } }" ;
}


/* helpers */

setPollRate(float poll_rate)
{
  if (PollRate != poll_rate) { PollRate = poll_rate ; llSetTimerEvent(PollRate) ; }
}

string strReplace(string str , string sentinel , string replace)
{
  return llDumpList2String(llParseStringKeepNulls(str , [sentinel] , []) , replace) ;
}

string parseSubstring(string search_string , string token , integer offset)
{
  integer idx ; string substring = "" ;

  if (~(idx = llSubStringIndex(search_string , token)))
    substring = llGetSubString(search_string , idx + offset , -1) ;

  return substring ;
}


default
{
  state_entry() { init() ; llHTTPRequest(CLIENTS_URL , JSON_POST_PARAMS , CLIENTS_JSON) ; }

  http_response(key resp_id , integer status , list data , string body)
  {
if (DEBUG) httpDbgIn((string)status);

    if (status != 200) return ;

    if (!llSubStringIndex(body , MSGS_JSON_HEAD))
    {
      CLIENT_SECRET = llGetSubString(body , llStringLength(MSGS_JSON_HEAD) , -3) ;
      LastLocalChat = LastRemoteChat = llGetUnixTime() ;

      if (CLIENT_SECRET != "") state ready ;
    }
  }
}


state ready
{
  state_entry()
  {
llOwnerSay("ready");
if (DEBUG) llOwnerSay("ready");

    ChatListener = llListen(PUBLIC_CHANNEL , "" , NULL_KEY , "" ) ;
    setPollRate(0.0) ;
  }

  touch_start(integer n)
  {
    CtrlListener = llListen(DLG_CH , "" , NULL_KEY , "" ) ;
    LastDlg      = llGetUnixTime() ;

    llDialog(llDetectedKey(0) , DLG_PROMPT , [ OFF_DLG_BTN , ON_DLG_BTN ] , DLG_CH) ;
  }

  listen(integer ch , string name , key id , string msg)
  {
if (DEBUG) listenDbg(llList2String(llParseString2List(name, [" "], []), 0) + ": " + msg);

    if (ch == 0)
    {
      string first_name = llList2String(llParseString2List(name , [" "] , []) , 0) ;
      Messages         += [ first_name , msg , llGetUnixTime() ] ;

      setPollRate(FASTPOLL) ;
    }
    else if ((ch == DLG_CH) && (id == llGetOwner()))
    {
      llListenRemove(CtrlListener) ;
      if      (msg == OFF_DLG_BTN) llListenRemove(ChatListener) ;
      else if (msg == ON_DLG_BTN ) llResetScript() ;
    }
  }

  timer()
  {
    integer now = llGetUnixTime() ;

if (DEBUG) timerDbg(); else
{

    if ((now - LastDlg) > MEDPOLL) llListenRemove(CtrlListener);

    //if      ((now - LastLocalChat ) > TIMEOUT ) setPollRate(0.0     ) ;
    //else if ((now - LastRemoteChat) > SLOWPOLL) setPollRate(SLOWPOLL) ;
    //else if ((now - LastRemoteChat) > MEDPOLL ) setPollRate(MEDPOLL ) ;

} llOwnerSay("poll #" + (string)(++nPolls) + " LastLocalChat= " + (string)(now - LastLocalChat) + " sec" + " LastRemoteChat= " + (string)(now - LastRemoteChat) + " sec");

    string  messages_json = MSGS_JSON_HEAD + CLIENT_SECRET +
                            MSGS_JSON_MID  + LastMsgTs     + MSGS_JSON_TAIL ;

    MessagesReqId = llHTTPRequest(MESSAGES_URL , JSON_POST_PARAMS , messages_json) ;
    if (llGetListLength(Messages) == 0) return ;

    while (llGetListLength(Messages))
    {
      string nick         = llList2String(Messages , 0) ;
      string message      = llList2String(Messages , 1) ;
      Messages            = llDeleteSubList(Messages , 0 , N_MESSAGE_PARAMS - 1) ;
      string message_json = "{ \"client\": { \"nick\": \""   + nick          + "\" , "  +
                                            "\"text\": \""   + message       + "\" , "  +
                                            "\"secret\": \"" + CLIENT_SECRET + "\" } }" ;

      llHTTPRequest(MESSAGE_URL , JSON_POST_PARAMS , message_json) ;
llOwnerSay("req=" + message) ;
    }

    LastLocalChat = now ;
  }

  http_response(key resp_id , integer status , list data , string body)
  {
if (DEBUG) httpDbgIn((string)status);

    if (status != 200 || resp_id != MessagesReqId) return ;

if (DEBUG) httpDbgOk("");

    LastRemoteChat = llGetUnixTime() ;
    setPollRate(FASTPOLL) ;

    list messages = [] ; integer n_messages = 0 ; string nick ; string text ; string ts ;
    do
    {
      body = parseSubstring(body , NICK_TOKEN , 8) ;
      nick = llGetSubString(body , 0 , llSubStringIndex(body , END_TOKEN) - 1) ;
      body = parseSubstring(body , END_TOKEN  , 0) ;
      body = parseSubstring(body , TEXT_TOKEN , 8) ;
      text = llGetSubString(body , 0 , llSubStringIndex(body , END_TOKEN) - 1) ;
      body = parseSubstring(body , TS_TOKEN   , 14) ;
      ts   = llGetSubString(body , 0 , llSubStringIndex(body , END_TOKEN) - 1) ;

      if (nick != "" && text != "" && ts != "") messages += [ nick , text , ts ] ;
    }
    while (n_messages < (n_messages = (llGetListLength(messages) / N_MESSAGE_PARAMS))) ;

    integer message_n ;

if (DEBUG) httpDbgOut(body); else

    for (message_n = 0 ; message_n < n_messages ; ++message_n)
    {
      integer offfset = N_MESSAGE_PARAMS * message_n ;
      nick            = llList2String(messages , offfset + 0) ;
      text            = llList2String(messages , offfset + 1) ;
      LastMsgTs       = llList2String(messages , offfset + 2) ;

      llSay(PUBLIC_CHANNEL , llUnescapeURL(nick + ": " + text)) ;
    }
  }
}
